/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "multibuffer.h"

namespace gimi {

MultiBuffer::MultiBuffer(const int defQueueSize,
                         ActiveStatus* const actStatus,
                         Connector *connPtr)
  : defaultQueueSize (defQueueSize),
    activeStatus (actStatus),
    bufferMutex (ownMutex_Init()),
    condHandle (ownCond_Init()),
    messageBuffers (),
    multiInfo (connPtr)
{
  assert(bufferMutex != NULL);
  assert(condHandle != NULL);
  assert(activeStatus != NULL);
  
  // Create the default buffer so it is ready for use from the start.
  messageBuffers.insert(t_multiBufferMapPair(GIMI_DEFAULTBUFFERID,
                                             GIMIMessageBuffer(defQueueSize)));
}
 
MultiBuffer::~MultiBuffer()
{
  // Go once into lock.
  ownMutex_Lock(bufferMutex);
  ownMutex_Unlock(bufferMutex);

  // Destroy.
  ownCond_Destroy(condHandle);
  ownMutex_Destroy(bufferMutex);

  // Mark pointers NULL to trace down possible "late accesses" to this
  // container.
  condHandle = NULL;
  bufferMutex = NULL;
}
      
int MultiBuffer::pushMessage(GIMIMessage *msg,
                             const int majorType,
                             const int minorType)
{
  t_multiBufferMapIterator mbIter;
  t_queueIdentifiers queues;
  
  unsigned int queueNo = 0;
  int result = GIMI_ERROR;
  bool insertCopy = false;
  
  ownMutex_Lock(bufferMutex);
  
  //// Find where this message should be inserted.
  if (!multiInfo.isEmpty()) { // There are multipliers.  
    queueNo = multiInfo.listQueues(queues, majorType, minorType,
                                   msg->getSenderId());
    dPrint(20, "Got %d redirections for %d:%d 0x%08x.", queueNo, majorType,
           minorType, msg->getSenderId());
  }
  // No multipliers at all, or no multipliers for this type, use default queue.
  if (queueNo == 0) {
    queueNo = 1;
    queues.insert(GIMI_DEFAULTBUFFERID);
    dPrint(20, "No redirections, add default-buffer to queues.");
  }
  
  //// Insert the given message to queues specified.
  //// First insert uses the original message, following inserts make a copy.
  for (t_queueIdentifiersConstIterator qIter = queues.begin();
      qIter != queues.end();
      ++qIter)
  {
    mbIter = messageBuffers.find(*qIter); // Find the right buffer.
    if (mbIter == messageBuffers.end()) { // Queue was not found, add it.
      std::pair< t_multiBufferMapIterator, bool > resultPair =
          messageBuffers.insert(t_multiBufferMapPair(*qIter, GIMIMessageBuffer(defaultQueueSize)));
      if (!resultPair.second) {
        dPrint(1, "Error! Failed to create messageBuffer with id %u.", *qIter);
      }
      dPrint(30, "Added messageBuffer with id %u", *qIter);
      mbIter = resultPair.first;
    }
    result = mbIter->second.pushMessage(msg, majorType, minorType, insertCopy);
    dPrint(25, "Message pushed to %d:%d in queue %u", majorType, minorType,
          *qIter);
    
    if (!insertCopy) { // Copy the message on all but first insert.
      insertCopy = true;
    }
  }
    
  ownMutex_Unlock(bufferMutex);
  
  return result; 
}

int MultiBuffer::popMessage(GIMIMessage **msg,
                            const int majorType,
                            const int minorType,
                            const int timeout,
                            const unsigned int bufferId /* = 0 */)
{
  t_multiBufferMapIterator mbIter;
  int result = GIMI_NOMSGS, timediffms = timeout;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    // Timeout-wait loop.
    gim::time currenttime(true), timeouttime;
    timeouttime = currenttime;
    timeouttime.setToCurrentPlus(timeout);
    result = mbIter->second.popMessage(msg, majorType, minorType);
    while (result == GIMI_NOMSGS && activeStatus->isActive()) {
      if (timeout) {
        if (timeout > 0) {
          currenttime.setToCurrent();
          timediffms = (timeouttime-currenttime).getTimeMs();
          if (currenttime > timeouttime) {
            result = GIMI_TIMEOUT;
            break;
          }
        }
        
        //dPrint(30, "Waiting for timeout %d", timediffms);
        if (ownCond_Wait(condHandle, bufferMutex, timediffms) == ownStatusTimeout) {
          result = GIMI_TIMEOUT;
          break;
        }

      } else {
        break;
      }
      
      mbIter = messageBuffers.find(bufferId);
      if (mbIter != messageBuffers.end()) {
        result = mbIter->second.popMessage(msg, majorType, minorType);
        
      } else {
                dPrintLCRed(ODTEST,"%p - MessageBuffer(%d,%d) disappeared while in middle of wait!", 
                  this, majorType, minorType);
        result = GIMI_INTERRUPT;
        break;
        
      }
    }
    if (!activeStatus->isActive()) {
      dPrintLCRed(ODTEST, "Waiting for message was interrupted, GIMI no longer active.");
      result = GIMI_INTERRUPT;
    }
  } else {
    result = GIMI_ERROR;
  }
  
  ownMutex_Unlock(bufferMutex);
  return result;
}

int MultiBuffer::popMessageRange(GIMIMessage **msg,
                                 const int majorTypeMin,
                                 const int majorTypeMax,
                                 const int minorTypeMin,
                                 const int minorTypeMax,
                                 const int timeout,
                                 const unsigned int bufferId /* = 0 */)
{
  t_multiBufferMapIterator mbIter;
  int result = GIMI_ERROR, timediffms = timeout;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    // Timeout-wait loop.
    gim::time currenttime(true), timeouttime;
    timeouttime = currenttime;
    timeouttime.setToCurrentPlus(timeout);
    result = mbIter->second.popMessageRange(msg, majorTypeMin, majorTypeMax,
                                            minorTypeMin, minorTypeMax);
    while (result == GIMI_NOMSGS && activeStatus->isActive()) {  
      if (timeout) {
        if (timeout > 0) {
          currenttime.setToCurrent();
          timediffms = (timeouttime-currenttime).getTimeMs();
          if (currenttime > timeouttime) {
            result = GIMI_TIMEOUT;
            break;
          }
        }
        //dPrint(30, "Waiting for timeout %d", timediffms);
        if (ownCond_Wait(condHandle, bufferMutex, timediffms) == ownStatusTimeout) {
          result = GIMI_TIMEOUT;
          break;
        }
      } else {
        break;
      }
      result = mbIter->second.popMessageRange(msg, majorTypeMin, majorTypeMax,
                                              minorTypeMin, minorTypeMax);
    }
    if (!activeStatus->isActive()) {
      dPrint(18, "Waiting for message was interrupted, GIMI no longer active.");
      result = GIMI_INTERRUPT;
    }
  } else {
    dPrint(2, "Buffer %d not found!", bufferId);
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}
  
int MultiBuffer::getQueueMaxSize(const int majorType,
                                 const int minorType,
                                 const unsigned int bufferId /* = 0 */) const
{
  t_multiBufferMapConstIterator mbIter;
  int result = GIMI_ERROR;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    result = mbIter->second.getQueueMaxSize(majorType, minorType);
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}


int MultiBuffer::getQueueCurSize(const int majorType,
                                 const int minorType,
                                 const unsigned int bufferId /* = 0 */) const
{
  t_multiBufferMapConstIterator mbIter;
  int result = GIMI_ERROR;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    result = mbIter->second.getQueueCurSize(majorType, minorType);
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}

int MultiBuffer::getTotalSize(const unsigned int bufferId /* = 0 */) const
{
  t_multiBufferMapConstIterator mbIter;
  int result = GIMI_ERROR;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    result = mbIter->second.getTotalSize();
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}

bool MultiBuffer::setQueueMaxSize(const int majorType,
                                  const int minorType,
                                  const int mSize,
                                  const unsigned int bufferId /* = 0 */)
{
  t_multiBufferMapIterator mbIter;
  bool result = false;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    result = mbIter->second.setQueueMaxSize(majorType, minorType, mSize);
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}

bool MultiBuffer::clearBuffer(const unsigned int bufferId /* = 0 */)
{
  t_multiBufferMapIterator mbIter;
  bool result = false;
  ownMutex_Lock(bufferMutex);
  mbIter = messageBuffers.find(bufferId);
  if (mbIter != messageBuffers.end()) { // Buffer found.
    result = true;
    mbIter->second.clearBuffer();
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}

unsigned int MultiBuffer::reserveBuffer()
{
  ownMutex_Lock(bufferMutex);
  unsigned int result = multiInfo.reserveQueue();
  if (result != 0) { // Reserved, add buffer.
    messageBuffers.insert(t_multiBufferMapPair(result, GIMIMessageBuffer(defaultQueueSize)));
  }
  ownMutex_Unlock(bufferMutex);
  return result;
}

bool MultiBuffer::releaseBuffer(const unsigned int queueIdentifier)
{
  ownMutex_Lock(bufferMutex);
  bool result = multiInfo.releaseQueue(queueIdentifier);
  if (result) { // Released, remove buffer.
    messageBuffers.erase(queueIdentifier);
    broadcastCond();
  }
  ownMutex_Unlock(bufferMutex);

  return result;
}

bool MultiBuffer::addRedirection(const unsigned int queueIdentifier,
                                 const int majorTypeId,
                                 const int minorTypeId /* = -1 */,
                                 const std::string &senderName /* = "" */,
                                 const int senderId /* = -1 */)
{
  ownMutex_Lock(bufferMutex);
  int result = multiInfo.addRedirection(queueIdentifier, majorTypeId, minorTypeId,
                                        senderName, senderId);
  ownMutex_Unlock(bufferMutex);
  return result;
}

bool MultiBuffer::removeRedirection(const unsigned int queueIdentifier,
                                    const int majorTypeId,
                                    const int minorTypeId /* = -1 */,
                                    const int senderId /* = -1 */)
{
  ownMutex_Lock(bufferMutex);
  int result = multiInfo.removeRedirection(queueIdentifier, majorTypeId,
                                           minorTypeId, senderId);
  broadcastCond();
  ownMutex_Unlock(bufferMutex);
  return result;
}

int MultiBuffer::listRedirections(t_redirectionList &redirections,
                                  const bool exactTypes /* = false */,
                                  const int majorTypeId /* = -1 */,
                                  const int minorTypeId /* = -1 */,
                                  const int senderId /* = -1 */)
{
  ownMutex_Lock(bufferMutex);
  int result = multiInfo.listRedirections(redirections, exactTypes, majorTypeId,
                                          minorTypeId, senderId);
  ownMutex_Unlock(bufferMutex);
  return result;
}

int MultiBuffer::renewRedirectionSenderIds()
{
  ownMutex_Lock(bufferMutex);
  int result = multiInfo.renewRedirectionSenderIds();
  ownMutex_Unlock(bufferMutex);
  return result;
}

void MultiBuffer::broadcastCond()
{
  ownCond_Broadcast(condHandle);
}
  
} // end namespace gimi
